#include "UMLCanvas.h"
#include "PaintVisitor.h"
#include "../model/UMLDiagram.h"
#include "../base/MouseListener.h"

#include <algorithm>
#include <QDebug>

UMLCanvas::UMLCanvas(UMLDiagram* diagram, QWidget* parent)
:	QWidget(parent),
	_diagram(diagram), _trans(10), _ml(NULL),
	_observer(*this) // safe, see DiagramObserver::DiagramObserver.
{
	setPalette(QPalette(Qt::white));   // set window color to white
	setAutoFillBackground(true);
	setMouseTracking(true);
	setFocusPolicy(Qt::StrongFocus); // enable keyboard focus for shortcuts

	diagram->attach(&_observer);
	connect(diagram, SIGNAL(objectRemoved(UMLObject*)),
			SLOT(objectRemoved(UMLObject*)));

	connect(&_selection, SIGNAL(changed(UMLObject*)),
			SLOT(selectionChanged(UMLObject*)));
}

UMLCanvas::~UMLCanvas()
{
	_diagram->unattach(&_observer);
}

QSize UMLCanvas::minimumSizeHint() const
{
	return QSize(400, 400);
}

QSize UMLCanvas::sizeHint() const
{
	using std::min;

	QSize dSize = _diagram->size();
	// Base it on diagram size, but only up to a reasonable maximum.
	return QSize(min(dSize.width(), 600), min(dSize.height(), 600));
}

UMLDiagram& UMLCanvas::diagram()
{
	return *_diagram;
}

const PointTranslator& UMLCanvas::trans() const
{
	return _trans;
}

const MouseListener* UMLCanvas::mouseListener() const
{
	return _ml;
}

void UMLCanvas::setMouseListener(MouseListener* ml)
{
	_ml = ml;
}

UMLObject* UMLCanvas::underPoint(const QPoint& logical) const
{
	// First check selection, we paint these on top
	SelectionMan::const_iterator s = find_if(
			_selection.begin(), _selection.end(), object_contains(logical));

	if (s != _selection.end())
		return *s;

	// If not select, find last-painted object under cursor
	UMLDiagram::const_reverse_iterator i = find_if(
			_diagram->rbegin(), _diagram->rend(), object_contains(logical));

	return (i != _diagram->rend()) ? *i : NULL;
}

void UMLCanvas::paintEvent(QPaintEvent*)
{
	QPainter painter(this);
	PaintVisitor v(_trans, painter, _selection);

	for (UMLDiagram::iterator iter = _diagram->begin();
			iter != _diagram->end(); ++iter)
		(*iter)->accept(v);
}

void UMLCanvas::mouseMoveEvent(QMouseEvent* ev)
{
	try
	{
		if (_ml)
			_ml->mouseMoveEvent(*ev);
	}
	catch (std::exception& ex)
	{
		qWarning("Caught exception during mouse move. Please save a copy of your work and exit. Details: %s.", ex.what());
	}
}

void UMLCanvas::mousePressEvent(QMouseEvent* ev)
{
	qDebug() << "Mouse clicked at: " << ev->pos();

	try
	{
		if (_ml)
			_ml->mousePressEvent(*ev);
	}
	catch (std::exception& ex)
	{
		qWarning("Caught exception during mouse press. Please save a copy of your work and exit. Details: %s.", ex.what());
	}
}

void UMLCanvas::mouseReleaseEvent(QMouseEvent* ev)
{
	try
	{
		if (_ml)
			_ml->mouseReleaseEvent(*ev);
	}
	catch (std::exception& ex)
	{
		qWarning("Caught exception during mouse release. Please save a copy of your work and exit. Details: %s.", ex.what());
	}
}

void UMLCanvas::selectionChanged(UMLObject*)
{
	// repaint canvas
	update();
}

void UMLCanvas::objectRemoved(UMLObject* obj)
{
	// don't leave removed objects selected
	if (_selection.contains(obj))
		_selection.toggle(obj);
}

UMLCanvas::DiagramObserver::DiagramObserver(UMLCanvas& canvas)
: _canvas(canvas)
{
}

void UMLCanvas::DiagramObserver::update(Observable*)
{
	_canvas.update();
}

